Skip to content

[NVBug: 6563509] Drop Phi-3-vision / Phi-4-multimodal PTQ support - #2115

Merged
cjluo-nv merged 10 commits into
mainfrom
chenjiel/nvbug-6563509-meta-init-fallback
Aug 10, 2026
Merged

[NVBug: 6563509] Drop Phi-3-vision / Phi-4-multimodal PTQ support#2115
cjluo-nv merged 10 commits into
mainfrom
chenjiel/nvbug-6563509-meta-init-fallback

Conversation

@cjluo-nv

@cjluo-nv cjluo-nv commented Aug 7, 2026

Copy link
Copy Markdown
Collaborator

What does this PR do?

Type of change: Deprecation

Resolves NVBug 6563509, where
hf_ptq.py on Phi-4-multimodal-instruct died with
RuntimeError: Tensor.item() cannot be called on meta tensors.

The crash is real but not fixable on our side, and it is not the reason the model
is unusable. Phi-4-multimodal's bundled remote code predates Transformers v5 and
does not load on any version in our supported range
(transformers>=4.57,<5.15):

Blocker Where
peft.get_peft_model reads prepare_inputs_for_generation, gone since transformers 4.52 dropped GenerationMixin from PreTrainedModel modeling_phi4mm.py:1959
_tied_weights_keys declared as a list; Transformers 5.x calls .keys() on it in post_init modeling_phi4mm.py:1937
int(torch.tensor(...)) in __init__, which cannot run on a meta device — the reported crash speech_conformer_encoder.py:1435

The model card pins transformers==4.48.2 / peft==0.13.2, so there is no
overlap with our floor and nothing on our side can bridge it. The model is
therefore dropped rather than worked around.

Phi-3-vision is dropped alongside it because it is the older, superseded model
in the same family
— with its successor unsupportable there is no reason to
keep carrying the predecessor. This is a product-scope call, not a separate
compatibility finding: Phi-3-vision shares the list-valued _tied_weights_keys
defect (modeling_phi3_v.py:1214) and so is likewise broken on Transformers 5.x,
but it does not hit the peft blocker, and it was not re-verified on 4.57.
Per the 0.46 changelog we have already bumped the floor to 4.57 and noted that
"Transformers 4.x support will be dropped in a future release", so any remaining
window closes on its own. Same reasoning already applied to VILA / NVILA in this
release.

Removed

  • the support-matrix row in examples/hf_ptq/README.md
  • "Phi4MMForCausalLM": "phi4mm" from MODEL_NAME_TO_TYPE
  • the multimodal-detection heuristics that only ever matched these two —
    vision_lora, audio_processor, embd_layer.image_embd_layer, and the
    phi4mm model-type check — in both is_multimodal_model and
    _is_multimodal_config
  • the Phi3Image / PhiImage exclusions in is_embedding
  • the phi4mm input-mode warning in hf_ptq.py
  • modelopt_recipes/huggingface/phi4mm/ and its references in
    modelopt_recipes/ptq.md

Not changed: the device-map sizing path (meta-device skeleton,
infer_auto_device_map, and the --gpu_max_mem_percentage cap) keeps its
original behavior. That cap is wanted exactly where it already fires — when the
model is already offloading to CPU, where it costs little and the headroom is
required. With the affected checkpoints removed, there is no supported model
that trips the meta-device build, so there is nothing to work around here.

Text-only Phi-3/Phi-4 and Phi-3.5-MoE are natively supported by
transformers and are untouched.

Testing

On H200, nvcr.io/nvidia/tensorrt-llm/release (torch 2.12, transformers 5.5.4),
against the real checkpoint:

  • Version matrix (vanilla transformers, no modelopt) — Phi-4-MM loads at
    4.48.2 / 4.49.0 / 4.50.0 / 4.51.3 and fails at 4.53.3 / 4.56.2 / 4.57.1
    (AttributeError: 'Phi4MMModel' object has no attribute 'prepare_inputs_for_generation') and at 5.5.4 (meta-init, then tied-keys).
    This is what establishes that no supported version works.
  • tests/examples/hf_ptq/test_example_utils.py — 28 passed.
  • Sweep: tests/examples/hf_ptq + tests/unit/torch/export — failure set
    identical to the pre-change tree (GPU/model-dependent test_vlm_ptq, plus
    test_quant_aware_conversion scoped-mapping tests), so none are introduced
    here.
  • pre-commit clean on all changed files, including recipe validation.

Before your PR is "Ready for review"

  • Is this change backward compatible?: ❌ — PTQ for Phi-3-vision and
    Phi-4-multimodal is removed, along with the huggingface/phi4mm/ptq/*
    recipes. Phi-4-multimodal is already unloadable on every supported transformers
    version, so no working workflow regresses; Phi-3-vision is a deliberate scope
    removal as its superseded predecessor.
  • If you copied code from any other sources or added a new PIP dependency, did you follow guidance in CONTRIBUTING.md: N/A
  • Did you write any new necessary tests?: N/A — this is a deletion; the existing
    test_get_model_* / test_resolve_init_config_* tests are unchanged and still
    pass.
  • Did you update Changelog?: ✅
  • Did you get Claude approval on this PR?: ❌ — not yet run.

Additional Information

Two related references were left in place deliberately; say the word and I'll
fold them in:

  • tests/examples/hf_ptq/test_deploy.py still deploys the already-published
    nvidia/Phi-4-multimodal-instruct-{NVFP4,FP8} checkpoints. Those artifacts
    exist and serve fine; this PR only removes the ability to produce them.
  • examples/torch_onnx/README.md still lists Phi-4-multimodal-instruct. That
    is a separate ONNX pipeline that does not go through get_model() and was not
    tested here.

Earlier revisions of this branch also reworked the device-map sizing so the
meta-tensor crash could not occur. That was reverted in 701180e: the guard is
correct as written, and every alternative either changed behavior for models that
fit today or moved the guard somewhere it does not belong, for a crash that only
ever affected the checkpoints this PR removes.

cjluo-nv and others added 2 commits August 7, 2026 07:44
… fails

get_model() builds a throwaway skeleton under
init_empty_weights(include_buffers=True) purely to size the model for
infer_auto_device_map. include_buffers=True makes accelerate push a global
torch.device("meta") context, so every tensor constructed in __init__ lands on
meta -- not just parameters and buffers. Remote-code checkpoints written before
Transformers v5 routinely derive scalar hyperparameters from real tensors there
(Phi-4-multimodal's conformer subsampling does int(torch.tensor(...))), which
raises "Tensor.item() cannot be called on meta tensors" and killed the whole
run before from_pretrained was ever reached.

Retry the skeleton without the global meta context, and if that also fails,
warn and skip the memory estimate instead of aborting -- from_pretrained can
still map the model on its own. Losing the estimate only costs the automatic
max_memory shrink, which --use_seq_device_map and --gpu_max_memory_percentage
already cover.

Note this does not by itself make Phi-4-multimodal-instruct loadable: its
remote code additionally needs transformers <4.52 (Phi4MMModel relies on
PreTrainedModel inheriting GenerationMixin, which peft's get_peft_model calls
into) and declares _tied_weights_keys as a list, which Transformers 5.x
rejects. Both are outside ModelOpt.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: Chenjie Luo <chenjiel@nvidia.com>
Both ship remote code that predates Transformers v5 and no longer loads on any
version this repo supports (transformers>=4.57,<5.15):

- Phi-4-multimodal needs transformers<4.52. Its __init__ calls
  peft.get_peft_model on Phi4MMModel, which reads prepare_inputs_for_generation
  -- present only while PreTrainedModel still inherited GenerationMixin.
  Verified loading at 4.48.2 / 4.49.0 / 4.50.0 / 4.51.3, failing at 4.53.3 /
  4.56.2 / 4.57.1 with AttributeError.
- Both declare _tied_weights_keys as a list; Transformers 5.x calls .keys() on
  it in post_init and raises AttributeError.
- Phi-4-multimodal additionally computes int(torch.tensor(...)) in __init__,
  which Transformers 5.x's meta-device from_pretrained cannot evaluate.

The model card pins transformers==4.48.2 / peft==0.13.2, so there is no overlap
with our floor and nothing on our side can bridge it.

Removes the support-matrix row, the phi4mm model type, the multimodal-detection
heuristics that only ever matched these two (vision_lora, audio_processor,
embd_layer.image_embd_layer), the Phi3Image/PhiImage embedding-export
exclusions, and modelopt_recipes/huggingface/phi4mm/. Text-only Phi-3/Phi-4 and
Phi-3.5-MoE are natively supported by transformers and are untouched.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: Chenjie Luo <chenjiel@nvidia.com>
@cjluo-nv
cjluo-nv requested review from a team as code owners August 7, 2026 20:39
@cjluo-nv
cjluo-nv requested review from meenchen and sugunav14 August 7, 2026 20:39
@coderabbitai

coderabbitai Bot commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

The change removes PTQ support for Phi-3-vision and Phi-4-multimodal. It removes related detection, export handling, documentation, and recipes. It also skips device-map memory estimation when model skeleton construction fails.

Changes

PTQ support removal

Layer / File(s) Summary
Remove Phi model detection and export exclusions
modelopt/torch/export/model_utils.py, modelopt/torch/export/layer_utils.py, examples/hf_ptq/example_utils.py
Removes Phi-4 multimodal classification and legacy multimodal detection. PhiImage and Phi3Image modules are now recognized as embeddings.
Update HF PTQ support and recipes
examples/hf_ptq/README.md, examples/hf_ptq/hf_ptq.py, modelopt_recipes/huggingface/phi4mm/..., modelopt_recipes/ptq.md, CHANGELOG.rst
Removes unsupported Phi models from the support matrix, removes the Phi-4 multimodal loading warning and recipes, and records the changes.

HF PTQ loading fallback

Layer / File(s) Summary
Retry model skeleton construction
examples/hf_ptq/example_utils.py, tests/examples/hf_ptq/test_example_utils.py, CHANGELOG.rst
Builds the sizing skeleton without buffers. If construction fails, the code warns, skips memory estimation, and continues model loading. Tests cover successful construction and failure handling.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Sequence Diagram(s)

sequenceDiagram
  participant HFPTQ as HF PTQ
  participant Skeleton as Meta skeleton construction
  participant DeviceMap as Device-map inference
  HFPTQ->>Skeleton: Build skeleton without buffers
  alt Construction succeeds
    HFPTQ->>DeviceMap: Infer device map
  else Construction fails
    Skeleton-->>HFPTQ: Return None and warning
    HFPTQ->>HFPTQ: Skip memory estimation
  end
Loading

Possibly related PRs

Suggested reviewers: sugunav14

🚥 Pre-merge checks | ✅ 6
✅ Passed checks (6 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Security Anti-Patterns ✅ Passed The PR adds no unsafe torch.load/numpy.load, hardcoded trust_remote_code=True, dynamic eval/exec, or # nosec; no dependency files changed, and trust_remote_code remains caller-controlled with defau...
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes the primary change: removing PTQ support for Phi-3-vision and Phi-4-multimodal.
✨ Finishing Touches 💡 1
⚔️ Resolve merge conflicts 💡
  • Resolve merge conflict in branch chenjiel/nvbug-6563509-meta-init-fallback
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch chenjiel/nvbug-6563509-meta-init-fallback

Comment @coderabbitai help to get the list of available commands.

@github-actions

github-actions Bot commented Aug 7, 2026

Copy link
Copy Markdown
Contributor
PR Preview Action v1.8.1
Preview removed because the pull request was closed.
2026-08-10 23:22 UTC

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Warning

CodeRabbit couldn't request changes on this pull request because it doesn't have sufficient GitHub permissions.

Please grant CodeRabbit Pull requests: Read and write permission and re-run the review.

👉 Steps to fix this

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@examples/hf_ptq/example_utils.py`:
- Around line 898-903: Update the fallback path after skeleton construction
fails, before the final from_pretrained load, to populate GPU entries in
model_kwargs["max_memory"] using the configured GPU memory percentage when model
is None. Preserve any limit already established by sequential device mapping,
and keep the existing fallback loading behavior unchanged.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: c15bf192-fc6f-44b2-86ab-c408eea1be07

📥 Commits

Reviewing files that changed from the base of the PR and between 75f6c81 and d94911b.

📒 Files selected for processing (10)
  • CHANGELOG.rst
  • examples/hf_ptq/README.md
  • examples/hf_ptq/example_utils.py
  • examples/hf_ptq/hf_ptq.py
  • modelopt/torch/export/layer_utils.py
  • modelopt/torch/export/model_utils.py
  • modelopt_recipes/huggingface/phi4mm/ptq/README.md
  • modelopt_recipes/huggingface/phi4mm/ptq/disabled_quantizers.yaml
  • modelopt_recipes/huggingface/phi4mm/ptq/nvfp4-kv_fp8_cast.yaml
  • modelopt_recipes/ptq.md
💤 Files with no reviewable changes (5)
  • examples/hf_ptq/hf_ptq.py
  • modelopt_recipes/huggingface/phi4mm/ptq/nvfp4-kv_fp8_cast.yaml
  • modelopt_recipes/huggingface/phi4mm/ptq/README.md
  • modelopt_recipes/huggingface/phi4mm/ptq/disabled_quantizers.yaml
  • modelopt/torch/export/model_utils.py

Comment thread examples/hf_ptq/example_utils.py Outdated
@codecov

codecov Bot commented Aug 7, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 76.71%. Comparing base (22b6a14) to head (c67c8b0).
⚠️ Report is 17 commits behind head on main.

Additional details and impacted files
@@            Coverage Diff             @@
##             main    #2115      +/-   ##
==========================================
- Coverage   78.60%   76.71%   -1.90%     
==========================================
  Files         522      522              
  Lines       60167    62613    +2446     
==========================================
+ Hits        47294    48033     +739     
- Misses      12873    14580    +1707     
Flag Coverage Δ
examples 42.92% <100.00%> (+1.05%) ⬆️
gpu 58.65% <0.00%> (-0.62%) ⬇️
regression 14.92% <0.00%> (+0.07%) ⬆️
unit 55.28% <0.00%> (-0.11%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Comment thread examples/hf_ptq/example_utils.py Outdated
config_dtype = _get_config_dtype(config_for_init)
model_kwargs2 = _apply_dtype_to_config(
model_kwargs, config_dtype, architecture, apply_config_dtype=True
# When computing the device_map, assuming bfloat16 precision by default,

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

how about we make this a util function?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done in 8444c11 — extracted as _build_meta_skeleton(), placed next to the other get_model helpers (_resolve_init_config / _get_config_dtype). get_model is now a single call, and the fallback is unit-testable on its own: verified all three paths (tier-1 succeeds / tier-1 fails on meta and tier-2 succeeds / both fail returning None with one warning).

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Follow-up: _build_meta_skeleton() simplified further in 1b062da. The two-tier retry is gone — it is now a single include_buffers=False build, because that is what Transformers' own from_pretrained uses and the old include_buffers=True probe was stricter than the loader it was predicting. Details in the design thread.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Final state on this one: the helper you asked me to extract has now been deleted outright in 4e0157d. Sizing the device map from the checkpoint index removes the need to construct a model at all, so _build_meta_skeleton and everything that fed it (_resolve_init_config, the from_config resolution, the accelerate imports) are gone. Net 74 lines lighter than before the extraction.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Superseded by 701180e — this code is back to its original form on main.

example_utils.py and its tests are restored to main except for the Phi-specific multimodal-detection heuristics. The meta-device skeleton, infer_auto_device_map, and the --gpu_max_mem_percentage cap all keep their original behavior, so there is nothing left in this thread to act on.

Reasoning: the cap is wanted exactly where it already fires — when the model is already offloading to CPU, where it costs little and the headroom is required. Every alternative explored on this branch (a more permissive skeleton, sizing from the checkpoint index, an unconditional budget) either changed behavior for models that fit today or moved the guard somewhere it does not belong, and all of it was for a crash that only ever affected the checkpoints this PR removes. NVBug 6563509 is resolved by dropping Phi-4-multimodal, not by changing the loader.

Comment thread examples/hf_ptq/README.md Outdated

> *This is a subset of the models supported. For the full list please check the [TensorRT-LLM support matrix](https://nvidia.github.io/TensorRT-LLM/reference/precision.html#support-matrix)*

> *Phi-3-vision and Phi-4-multimodal were dropped from this matrix: their bundled

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no need to explain here.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done in 8444c11 — note removed. The CHANGELOG entry under 0.46 Backward Breaking Changes carries the explanation.

@meenchen meenchen left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bot review (gpt-5.6-sol) — DM the bot to share feedback.

The fallback direction is reasonable, but there is a correctness issue in the failure path and the breaking Phi-3-vision removal is not supported by the compatibility evidence presented. No tests were added for the new two-attempt/fallback behavior.

Design review: the problem is to prevent an optional meta skeleton used for device-map sizing from aborting an otherwise viable from_pretrained load. Existing alternatives are (1) the repo's build_meta_causal_lm pattern in modelopt/torch/utils/plugins/model_load_utils.py, which uses init_empty_weights(include_buffers=False) directly, (2) relying on Accelerate/Transformers' existing device_map="auto" plus an explicit max_memory, or (3) extending/using Transformers' safe meta creation patches. The PR body explains why include_buffers=True is hazardous and why it retries, but does not compare why retrying is preferable to always using the existing include_buffers=False pattern or delegating directly to from_pretrained. Please document that choice, as required by the design-review gate.


Additional comments (outside the PR diff):

  • examples/hf_ptq/example_utils.py:909 — > Bot comment.

The suggested --gpu_max_memory_percentage mitigation has no effect when both skeleton attempts fail. In this branch model is None, so the later elif model is not None block never scales max_memory or places it in model_kwargs; from_pretrained(device_map="auto") therefore uses its default memory budget regardless of this flag. Please either pass an appropriately scaled max_memory in the fallback path or remove this recommendation. Add mocked tests for first-attempt failure/second-attempt success and both-attempts failure; those would also pin whether infer_auto_device_map is skipped and whether the advertised memory limit reaches from_pretrained.

Comment thread CHANGELOG.rst Outdated

**Deprecations**

- Drop PTQ support for **Phi-3-vision** and **Phi-4-multimodal**. Their bundled remote code predates Transformers v5 and no longer loads on the versions this repo requires (``transformers>=4.57``): Phi-4-multimodal needs ``transformers<4.52`` because it reaches ``prepare_inputs_for_generation`` through ``peft``, which requires ``PreTrainedModel`` to still inherit ``GenerationMixin``, and both models declare ``_tied_weights_keys`` as a list, which Transformers 5.x rejects. Removes the ``phi4mm`` model type, its multimodal-detection heuristics (``vision_lora`` / ``audio_processor`` / ``embd_layer.image_embd_layer``), the ``Phi3Image`` / ``PhiImage`` embedding-export exclusions, and the ``modelopt_recipes/huggingface/phi4mm/`` recipes.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bot comment.

The supplied evidence does not establish that Phi-3-vision is unloadable across the supported range. The dependency floor and CI matrix explicitly include Transformers 4.57, while the listed Phi-3 blocker (_tied_weights_keys as a list) is described as a Transformers 5.x failure; the version matrix in the PR body only tests Phi-4-MM. Since this is a breaking support removal, please provide/test the Phi-3-vision failure on 4.57 (and identify its blocker), or retain Phi-3-vision support and scope the deletion/documentation to Phi-4-MM.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fair reading of the evidence, but we are keeping Phi-3-vision in the removal.

You are right about what was and was not proven. Phi-3-vision has no get_peft_model call, so it does not have the transformers-4.52 blocker — that one is Phi-4-MM only. The defect I verified for it is _tied_weights_keys declared as a list (modeling_phi3_v.py:1214), which is a Transformers 5.x failure, and I did not run it end-to-end on 4.57.

We are dropping it anyway, for product reasons rather than a 4.57 repro: Phi-3-vision is the older, superseded model, and Phi-4-multimodal is its replacement. If we cannot support the successor there is no reason to carry the predecessor — it is strictly the weaker model and nobody starting today would pick it over Phi-4-MM.

The technical direction agrees. Phi-3-vision is confirmed broken on Transformers 5.x, and per the 0.46 changelog we already bumped the floor to 4.57 with "Transformers 4.x support will be dropped in a future release". So the best case for keeping it is a support window that closes on its own within a release or two, on a model whose remote code Microsoft is not updating. The same reasoning already applied to VILA / NVILA in this release.

Happy to reopen if someone has an active Phi-3-vision workflow on 4.57.

cjluo-nv and others added 2 commits August 7, 2026 22:09
0.46 is the release these land in, not the still-open 0.47 section. The Phi
drop goes under Backward Breaking Changes next to the VILA / NVILA entry, which
removed model support for the same reason (bundled remote code pinned below our
transformers floor); the skeleton fallback goes under Bug Fixes.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: Chenjie Luo <chenjiel@nvidia.com>
… note

- Extract the two-attempt skeleton build into `_build_meta_skeleton()` next to
  the other `get_model` helpers (@cjluo-nv). `get_model` now reads as one line,
  and the fallback logic is unit-testable in isolation.
- Fix the OOM hint in the fallback warning. It named `--gpu_max_memory_percentage`,
  which does not exist -- the flag is `--gpu_max_mem_percentage` -- and on its own
  that flag has no effect when the skeleton fails: `model_kwargs["max_memory"]` is
  only set by the `_disk_offload` and `use_seq_device_map` paths, so the skipped
  `infer_auto_device_map` branch never applies it. Point at `--use_seq_device_map`
  (which does apply the percentage) and `--batch_size` instead (@coderabbitai).
  Not adopting the suggested unconditional cap: the original code only shrinks
  `max_memory` when `infer_auto_device_map` reports a CPU spill, so capping
  whenever the skeleton fails would force CPU offload onto models that currently
  fit entirely on GPU.
- Drop the support-matrix explanation note; the CHANGELOG covers it (@cjluo-nv).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: Chenjie Luo <chenjiel@nvidia.com>

@meenchen meenchen left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bot review (gpt-5.6-sol) — DM the bot to share feedback.

Re-review: the prior max-memory failure-path concern is addressed by the revised warning, and extracting _build_meta_skeleton makes the fallback testable. However, two critical items remain: no tests were committed for the new retry/skip behavior, and the Phi-3-vision compatibility claim still overstates the evidence (the author explicitly confirmed it was not tested on supported Transformers 4.57 and is being removed for product reasons). The changelog also retains the obsolete CLI spelling. Design review remains incomplete: the problem is preventing an optional sizing skeleton from aborting the real load; alternatives include the repo's direct include_buffers=False approach in modelopt/torch/utils/plugins/model_load_utils.py, delegating to Transformers/Accelerate device_map="auto" with max_memory, and Transformers' safe meta-creation patches. The PR discusses the last two and why global-meta creation is hazardous, but still does not explain why the two-tier retry is preferable to always using the existing include_buffers=False pattern. Please document that tradeoff before approval.

Comment thread examples/hf_ptq/example_utils.py Outdated
return hf_config


def _build_meta_skeleton(from_config, config_for_init, model_kwargs, architecture):

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bot comment.

This addresses the implementation shape, but the prior critical test request is still unresolved: none of the changed files adds tests for this helper. Please commit mocked coverage for (1) include_buffers=True succeeding, (2) the first attempt failing and include_buffers=False succeeding, and (3) both attempts failing with one warning. The last case should also exercise get_model to pin that infer_auto_device_map is skipped while from_pretrained is still called. Existing test_get_model_* tests only exercise a successful skeleton.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Both points addressed in 1b062da — and you were right that the retry needed justifying. It did not survive the justification, so it is gone.

Design. The probe answers exactly one boolean: will the model spill to CPU, so should max_memory get the gpu_max_mem_percentage haircut. (inferred_device_map has two references in the file — it is built, tested with "cpu" in .values(), and discarded.) infer_auto_device_map reaches that via compute_module_sizes, which is tensor.numel() * dtype_byte_size(tensor.dtype) — shapes and dtypes, never storage.

So the probe only has to reproduce the module tree, and the binding constraint is that it must be no stricter than the loader it predicts:

how from_pretrained builds the model
transformers 4.57.6 init_contexts = [no_init_weights(), init_empty_weights()]include_buffers defaults to False (modeling_utils.py:4378)
transformers 5.5.4 torch.device("meta") + meta_device_safe_creation_ops() (redirects torch.linspace to CPU)
old probe bare torch.device("meta"), no patch

include_buffers=True was stricter than both. accelerate implements it as a bare global device context, so it captures scratch arithmetic in __init__ that has nothing to do with weights. Transformers added meta_device_safe_creation_ops precisely because pre-v5 remote code derives scalars that way. A model doing int(torch.tensor(...)) in __init__ loads fine via from_pretrained on 4.x and 5.x, but died in our probe — an optimization killing runs it exists only to speed up.

So the answer to "why not just use the existing include_buffers=False pattern" is: we should, and now do. Single build, no retry loop, no noqa: PERF203, no error accumulation. Measured cost on real checkpoints is nil — Qwen3-8B and DeepSeek-R1-Distill-Llama-70B both retain 0.0 MiB and produce identical sized totals (15.256 / 131.417 GiB) either way, for +0.09s and +0.19s.

One note on build_meta_causal_lm: it uses include_buffers=False for a different reason, not as a general preference. Its skeleton is kept and has real weights loaded into it under FSDP2, and inv_freq / original_inv_freq are persistent=False (verified absent from state_dict()), so they are computed at init and never loaded — on meta they would stay meta. It needs real buffers; this probe needs none. Same call, opposite motivation.

Tests. Added in tests/examples/hf_ptq/test_example_utils.py: the probe uses permissive patching, it survives a meta-hostile __init__ (int(torch.tensor(...)), the Phi-4-MM shape), it returns None with a warning on failure, and get_model skips infer_auto_device_map while still calling from_pretrained — asserting no max_memory cap is invented, per the CodeRabbit thread. 32 passed in the file.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Follow-up: the helper this thread was about no longer exists as of 4e0157d. _build_meta_skeleton and its four tests are gone — the device map is now sized from the checkpoint on disk, so no model is constructed and there is no fallback path to cover. The replacement _checkpoint_size_bytes has its own tests (index metadata, shard-sum fallback, malformed index) plus get_model tests for capping only when the weights exceed the GPU budget, no cap when the size is unknown, and an assertion that nothing is constructed during sizing.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Superseded by 701180e — this code is back to its original form on main.

example_utils.py and its tests are restored to main except for the Phi-specific multimodal-detection heuristics. The meta-device skeleton, infer_auto_device_map, and the --gpu_max_mem_percentage cap all keep their original behavior, so there is nothing left in this thread to act on.

Reasoning: the cap is wanted exactly where it already fires — when the model is already offloading to CPU, where it costs little and the headroom is required. Every alternative explored on this branch (a more permissive skeleton, sizing from the checkpoint index, an unconditional budget) either changed behavior for models that fit today or moved the guard somewhere it does not belong, and all of it was for a crash that only ever affected the checkpoints this PR removes. NVBug 6563509 is resolved by dropping Phi-4-multimodal, not by changing the loader.

Comment thread CHANGELOG.rst
- Remove the deprecated ``examples/llm_qad`` Megatron-LM QAD example (deprecated in 0.45). Use the `megatron_bridge QAD example <https://github.com/NVIDIA/Model-Optimizer/tree/main/examples/megatron_bridge#quantization-aware-distillation-qad>`_ instead, which provides a simpler Python-based interface and better model coverage.
- Dropped VILA / NVILA vision-language model support in ``examples/hf_ptq``. VILA's modeling code requires ``transformers<=4.50.0``, which conflicts with ModelOpt's minimum supported ``transformers`` version. The VILA-specific bootstrap (repo clone, ``requirements-vila.txt``) and loading paths in ``example_utils.py`` have been removed.
- Dropped **Phi-3-vision** and **Phi-4-multimodal** PTQ support in ``examples/hf_ptq`` (NVBug 6563509). Their bundled remote code predates Transformers v5 and no longer loads on any version ModelOpt supports (``transformers>=4.57``): Phi-4-multimodal requires ``transformers<4.52`` because it reaches ``prepare_inputs_for_generation`` through ``peft``, which needs ``PreTrainedModel`` to still inherit ``GenerationMixin``, and both models declare ``_tied_weights_keys`` as a list, which Transformers 5.x rejects. The support-matrix row, the ``phi4mm`` model type, the multimodal-detection heuristics that only ever matched these two (``vision_lora`` / ``audio_processor`` / ``embd_layer.image_embd_layer``), the ``Phi3Image`` / ``PhiImage`` embedding-export exclusions, and the ``modelopt_recipes/huggingface/phi4mm/`` recipes have been removed. Text-only Phi-3/Phi-4 and Phi-3.5-MoE are unaffected.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bot comment.

This still says Phi-3-vision “no longer loads on any version ModelOpt supports,” but the author’s reply confirms it was not tested on supported Transformers 4.57 and that its identified _tied_weights_keys blocker is a 5.x failure. If the removal is a product decision because the model is superseded, document that rationale instead of making an unverified compatibility claim (or provide the 4.57 repro/blocker).

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Addressed in 17dfd3b — you were right that the entry was making an unverified claim.

The changelog now splits the two rationales. Phi-4-multimodal is described as unloadable on any supported version, which the matrix does support. Phi-3-vision is described as a supersession removal: "dropped alongside it: it is the older, superseded model in the same family, so with its successor unsupportable there is no reason to keep carrying the predecessor." A parenthetical records what was actually verified — it shares the list-valued _tied_weights_keys defect so is broken on Transformers 5.x, but does not hit the peft blocker. No 4.57 claim is made for it. The PR description carries the same split.

Comment thread CHANGELOG.rst Outdated
- Fix ``examples/vllm_serve`` serving shared experts uncalibrated: their ``gate_proj``/``up_proj`` quantizer keys were not merged into ``gate_up_proj`` on reload, so they matched no module and were dropped.
- Fix Qwen3-VL MoE PTQ failing on ``transformers>=5.12`` with ``AttributeError: 'QuantQwen3VLMoeTextExperts' object has no attribute 'hidden_size'`` (NVBug 6518551). transformers 5.12 moved ``Qwen3VLMoeTextExperts`` onto the standard ``@use_experts_implementation`` fused layout (``hidden_size``/``expert_dim`` renamed to ``hidden_dim``/``intermediate_dim``, ``gate_up_proj`` transposed to ``(num_experts, 2*intermediate_dim, hidden_dim)``, two ``F.linear`` calls per expert), but the legacy ``_QuantQwen3VLMoeTextExperts`` wrapper stayed statically registered and shadowed on-the-fly detection. The new layout is now left to ``register_fused_experts_on_the_fly``, which claims it with the generic ``_QuantFusedExperts``; the legacy wrapper is still registered on ``transformers<5.12``, whose ``torch.bmm``-based forward the generic wrapper cannot intercept.
- Fix ``examples/hf_ptq`` multi-node FSDP2 export (``--use_fsdp2``) failing with ``RuntimeError: Cannot set version_counter for inference tensor``. ``export_quantized`` wrapped its whole body in ``torch.inference_mode()``, so the full params gathered by ``get_model_state_dict(full_state_dict=True)`` were inference tensors and the subsequent ``state_dict()`` -> ``param.detach()`` could not set their version counter. The export context is now ``torch.no_grad()``, which still disables autograd but keeps the gathered params as normal tensors.
- Fix ``examples/hf_ptq`` aborting the whole model load when the throwaway meta-device skeleton it builds to size ``infer_auto_device_map`` cannot be constructed (NVBug 6563509). ``init_empty_weights(include_buffers=True)`` pushes a global ``torch.device("meta")`` context, so remote-code checkpoints that derive scalar hyperparameters from real tensors in ``__init__`` failed with ``Tensor.item() cannot be called on meta tensors`` before ``from_pretrained`` was ever reached. The skeleton is now retried without the global meta context, and if that also fails the memory estimate is skipped with a warning instead of failing the run; ``--use_seq_device_map`` and ``--gpu_max_memory_percentage`` cover the lost heuristic.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bot comment.

The fallback warning was corrected, but this changelog entry still uses the nonexistent --gpu_max_memory_percentage spelling and implies that flag independently covers the lost estimate. The actual option is --gpu_max_mem_percentage, and in this failure path it only takes effect with --use_seq_device_map; please mirror the accurate warning text here.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Addressed — first in 1b062da, and the entry has since been rewritten again in 4e0157d.

You were right on both parts: the changelog still carried the nonexistent --gpu_max_memory_percentage spelling, and it implied that flag alone covered the lost estimate when in this path it only takes effect together with --use_seq_device_map.

That text is now gone entirely rather than corrected, because the fallback it described no longer exists: the device map is sized from the checkpoint on disk instead of from a constructed model, so there is no skeleton to fail and no estimate to lose. The entry now describes that mechanism, and the only remaining degradation is "size unreadable → no cap applied", the same as before.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Superseded by 701180e — this code is back to its original form on main.

example_utils.py and its tests are restored to main except for the Phi-specific multimodal-detection heuristics. The meta-device skeleton, infer_auto_device_map, and the --gpu_max_mem_percentage cap all keep their original behavior, so there is nothing left in this thread to act on.

Reasoning: the cap is wanted exactly where it already fires — when the model is already offloading to CPU, where it costs little and the headroom is required. Every alternative explored on this branch (a more permissive skeleton, sizing from the checkpoint index, an unconditional budget) either changed behavior for models that fit today or moved the guard somewhere it does not belong, and all of it was for a crash that only ever affected the checkpoints this PR removes. NVBug 6563509 is resolved by dropping Phi-4-multimodal, not by changing the loader.

The probe exists only to answer one boolean -- will this model spill to CPU, so
should max_memory get the gpu_max_mem_percentage haircut. `infer_auto_device_map`
gets there via `compute_module_sizes`, which reads `tensor.numel()` and
`tensor.dtype` and never touches storage. So the probe only has to reproduce the
module tree, and it must be no stricter than the loader it predicts.

`include_buffers=True` was stricter than both loaders:

- Transformers 4.x `from_pretrained` builds under `init_empty_weights()`, whose
  `include_buffers` defaults to False (modeling_utils.py:4378).
- Transformers 5.x builds under `torch.device("meta")` plus
  `meta_device_safe_creation_ops()`, which redirects `torch.linspace` to CPU
  precisely so pre-v5 remote code that derives scalars in `__init__` keeps
  working.

accelerate implements `include_buffers=True` as a bare global `torch.device("meta")`
context, which also captures scratch arithmetic in `__init__` that has nothing to
do with weights. A model doing `int(torch.tensor(...))` there loads fine through
`from_pretrained` on both 4.x and 5.x but died in our probe -- an optimization
killing runs it was only meant to speed up.

Dropping to a single `include_buffers=False` build removes the retry loop, the
PERF203 waiver, and the two-attempt error accumulation. Measured cost on real
checkpoints is nil: Qwen3-8B and DeepSeek-R1-Distill-Llama-70B both report 0.0 MiB
retained and identical sized totals (15.256 / 131.417 GiB) either way, for +0.09s
and +0.19s respectively.

Adds the coverage requested in review: the probe uses permissive patching, it
survives a meta-hostile `__init__`, it returns None and warns on failure, and
`get_model` skips `infer_auto_device_map` while still calling `from_pretrained`
without inventing a max_memory cap.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: Chenjie Luo <chenjiel@nvidia.com>

@meenchen meenchen left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bot review (gpt-5.6-sol) — DM the bot to share feedback.

Re-review: the implementation concern and most prior comments are addressed. The max-memory finding was correctly resolved by fixing the warning rather than inventing a fallback cap; the CLI spelling is now correct; and the new tests cover permissive skeleton construction, a meta-hostile initializer, helper failure/warning, and the get_model skip-and-continue path.

One important prior comment remains unresolved: the changelog still says Phi-3-vision cannot load on any supported Transformers version, although the author confirmed it was not tested on supported 4.57 and is being removed for product/supersession reasons. The PR body also remains stale: it describes and claims testing for a two-attempt retry that commit 1b062da36 removed. Please update both to describe the current single include_buffers=False probe and accurately characterize the Phi-3-vision removal.

Design review: the problem is preventing an optional model-sizing skeleton from aborting a potentially viable real load. The relevant alternatives are the repo's existing build_meta_causal_lm pattern (modelopt/torch/utils/plugins/model_load_utils.py) using include_buffers=False, delegating sizing/mapping to Accelerate/Transformers via device_map="auto" and max_memory, and Transformers' patched safe-meta construction. The author's design-thread reply now gives a convincing reason to adopt the first option, and the code/docstring do so, but the required PR-body rationale is not yet updated and currently documents the superseded retry design.

This PR also deletes recipe files carrying SPDX/license headers, so the licensing-related file removal should receive human sign-off before merge.

Comment thread CHANGELOG.rst Outdated
- Remove the deprecated ``examples/llm_autodeploy`` example (deprecated in 0.45). Use TensorRT-LLM's `AutoDeploy <https://github.com/NVIDIA/TensorRT-LLM/tree/main/examples/auto_deploy>`_ directly together with ModelOpt PTQ in ``examples/hf_ptq``.
- Remove the deprecated ``examples/llm_qad`` Megatron-LM QAD example (deprecated in 0.45). Use the `megatron_bridge QAD example <https://github.com/NVIDIA/Model-Optimizer/tree/main/examples/megatron_bridge#quantization-aware-distillation-qad>`_ instead, which provides a simpler Python-based interface and better model coverage.
- Dropped VILA / NVILA vision-language model support in ``examples/hf_ptq``. VILA's modeling code requires ``transformers<=4.50.0``, which conflicts with ModelOpt's minimum supported ``transformers`` version. The VILA-specific bootstrap (repo clone, ``requirements-vila.txt``) and loading paths in ``example_utils.py`` have been removed.
- Dropped **Phi-3-vision** and **Phi-4-multimodal** PTQ support in ``examples/hf_ptq`` (NVBug 6563509). Their bundled remote code predates Transformers v5 and no longer loads on any version ModelOpt supports (``transformers>=4.57``): Phi-4-multimodal requires ``transformers<4.52`` because it reaches ``prepare_inputs_for_generation`` through ``peft``, which needs ``PreTrainedModel`` to still inherit ``GenerationMixin``, and both models declare ``_tied_weights_keys`` as a list, which Transformers 5.x rejects. The support-matrix row, the ``phi4mm`` model type, the multimodal-detection heuristics that only ever matched these two (``vision_lora`` / ``audio_processor`` / ``embd_layer.image_embd_layer``), the ``Phi3Image`` / ``PhiImage`` embedding-export exclusions, and the ``modelopt_recipes/huggingface/phi4mm/`` recipes have been removed. Text-only Phi-3/Phi-4 and Phi-3.5-MoE are unaffected.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bot comment.

This prior compatibility comment is still unresolved. The author confirmed Phi-3-vision was not tested on supported Transformers 4.57 and that its identified _tied_weights_keys failure applies to 5.x, so “no longer loads on any version ModelOpt supports” remains unsupported for that model. Please describe Phi-3-vision as a product/supersession removal (using the rationale from the author's reply), or provide the missing 4.57 repro/blocker. Keep the technical incompatibility claim scoped to Phi-4-MM where the version matrix supports it.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Addressed in 17dfd3b — you were right that the entry was making an unverified claim.

The changelog now splits the two rationales. Phi-4-multimodal is described as unloadable on any supported version, which the matrix does support. Phi-3-vision is described as a supersession removal: "dropped alongside it: it is the older, superseded model in the same family, so with its successor unsupportable there is no reason to keep carrying the predecessor." A parenthetical records what was actually verified — it shares the list-valued _tied_weights_keys defect so is broken on Transformers 5.x, but does not hit the peft blocker. No 4.57 claim is made for it. The PR description carries the same split.

Comment thread examples/hf_ptq/example_utils.py Outdated

# Only a sizing aid for ``infer_auto_device_map`` below; ``None`` when the model
# cannot be built on meta, in which case the estimate is skipped.
model = _build_meta_skeleton(from_config, config_for_init, model_kwargs2, architecture)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bot comment.

[P1] Please release this parameter-only skeleton before loading the checkpoint. With include_buffers=False, parameters are meta tensors, but registered buffers and other tensors retained by the constructor are real CPU allocations. This local remains referenced while the right-hand side of the later model = auto_model_module.from_pretrained(...) materializes the actual checkpoint, so the two allocations overlap; disk-offload also builds the skeleton even though that branch never calls infer_auto_device_map. For models with large constructor buffers or caches, this can bypass max_cpu_memory_gb and OOM the memory-constrained path. Please skip skeleton construction in disk-offload mode and release model after device-map inference, before from_pretrained().

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Both points were correct, and they pushed the design somewhere better — addressed in 4e0157d, though not the way you suggested.

You were right on both counts against the code as it stood: Python evaluates the right-hand side before rebinding, so model = auto_model_module.from_pretrained(...) kept the probe alive for the whole checkpoint load, and with include_buffers=False its buffers are real CPU allocations. And the _disk_offload branch never touched the probe, so it was paying for one unused in exactly the memory-constrained path.

Rather than release the probe and skip it in disk-offload, the probe is gone. It only ever produced one boolean — will the weights fit on the GPUs, so should max_memory get the haircut — and that number is already on disk. metadata.total_size in the safetensors index equals what compute_module_sizes computed on the constructed model, exactly: Qwen3-8B 16381470720 B = 15.256 GiB, DeepSeek-R1-Distill-Llama-70B 141107412992 B = 131.417 GiB, both matching the meta model.

So _checkpoint_size_bytes() reads the index (falling back to summing shards, None if unreadable), and no model is constructed during device-map selection. That removes the overlap you flagged by construction, removes the disk-offload waste, and removes the original NVBug crash site entirely rather than tolerating it — Phi-4-multimodal now sizes at 10.383 GiB and reaches from_pretrained with no meta-tensor error. _build_meta_skeleton, its fallback warning, _resolve_init_config, the from_config resolution, and the accelerate init_empty_weights / infer_auto_device_map imports all went with it. Net 74 lines lighter.

Known approximation, worth flagging: total-bytes-vs-summed-GPU-budget ignores the per-device packing and no-split-module modelling infer_auto_device_map did. For a heuristic that only chooses whether to apply an 80% haircut, total size is the dominant term.

Tests cover the index path, the shard-sum fallback, a malformed index, capping only when the weights exceed the budget, and no cap when the size is unknown; the get_model tests also assert nothing is constructed during sizing.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Superseded by 701180e — this code is back to its original form on main.

example_utils.py and its tests are restored to main except for the Phi-specific multimodal-detection heuristics. The meta-device skeleton, infer_auto_device_map, and the --gpu_max_mem_percentage cap all keep their original behavior, so there is nothing left in this thread to act on.

Reasoning: the cap is wanted exactly where it already fires — when the model is already offloading to CPU, where it costs little and the headroom is required. Every alternative explored on this branch (a more permissive skeleton, sizing from the checkpoint index, an unconditional budget) either changed behavior for models that fit today or moved the guard somewhere it does not belong, and all of it was for a crash that only ever affected the checkpoints this PR removes. NVBug 6563509 is resolved by dropping Phi-4-multimodal, not by changing the loader.

cjluo-nv and others added 2 commits August 10, 2026 16:46
Phi-4-multimodal is dropped because it cannot load on any supported transformers
version. Phi-3-vision is dropped because it is the superseded predecessor in the
same family -- with the successor unsupportable there is no reason to keep the
older model -- not because an equivalent 4.57 repro exists for it. The previous
wording ran both models' rationale together and implied Phi-3-vision had been
shown unloadable across the whole supported range, which overstates what was
verified: it shares the list-valued _tied_weights_keys defect (so is broken on
Transformers 5.x) but does not hit the peft/GenerationMixin blocker.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: Chenjie Luo <chenjiel@nvidia.com>
get_model builds a device map to leave GPU headroom for calibration activations,
and needs one boolean to do it: will the weights fit on the GPUs? It answered
that by constructing a throwaway meta-device model and running
infer_auto_device_map on it, then discarding the map and keeping only
`"cpu" in .values()`.

Constructing a model to learn its size means running the checkpoint's __init__,
which is where this bug lives: remote code that derives a scalar from a real
tensor (Phi-4-multimodal's conformer does int(torch.tensor(...))) cannot run
under a meta device context. Both Transformers 4.x and 5.x load those models
fine, so an optimization was killing loads it existed only to speed up.

The number is already on disk. `metadata.total_size` in the safetensors index is
the same sum `compute_module_sizes` computes -- exactly, verified on Qwen3-8B
(16381470720 B = 15.256 GiB) and DeepSeek-R1-Distill-Llama-70B (141107412992 B =
131.417 GiB), matching what the meta model reported. `_checkpoint_size_bytes`
reads it, falling back to summing shard sizes, and returns None when the layout
is unreadable (no cap applied, as before).

This removes the failure mode rather than tolerating it, and with it the
retry/fallback machinery it needed: `_build_meta_skeleton`, its warning path,
the `del` that kept the probe's real buffers from overlapping the checkpoint
load, the disk-offload special case, `_resolve_init_config` and the `from_config`
resolution that fed it, and the accelerate `init_empty_weights` /
`infer_auto_device_map` imports. Net 74 lines lighter.

config_dtype now comes from hf_config rather than the re-derived built-in config;
both read the same config.json dtype field.

Tests cover the index-metadata path, the shard-sum fallback, a malformed index,
capping only when the weights exceed the GPU budget, and no cap when the size is
unknown. Verified end to end that Phi-4-multimodal now sizes at 10.383 GiB and
reaches from_pretrained without a meta-tensor error.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: Chenjie Luo <chenjiel@nvidia.com>
Comment thread examples/hf_ptq/example_utils.py Outdated
return hf_config
builtin_config_kwargs = {k: v for k, v in config_kwargs.items() if k != "trust_remote_code"}
path = Path(ckpt_path)
if not path.is_dir():

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bot comment.

[P1] Preserve sizing for Hugging Face model IDs. This returns None whenever ckpt_path is not a local directory, but --pyt_ckpt_path explicitly accepts a Hugging Face model name and the README recommends passing the model card directly. Every org/model invocation therefore bypasses the headroom logic below; a large Hub model that device_map=auto spills to CPU can again fill GPU memory and OOM during calibration. Please resolve or download the small checkpoint index, or query Hub file metadata/cache, before returning unknown, and add a repository-ID test. Reserve the None fallback for genuinely unreadable layouts.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Correct, and it is one of the reasons the sizing logic is now gone entirely in 1b6a79d.

You were right about the gap: --pyt_ckpt_path accepts a model card (its help says so, and the README uses openai/gpt-oss-20b at line 245), hf_ptq.py:578 passes it straight into get_model unresolved, and _checkpoint_size_bytes bailed on anything that was not a local directory. Every hub-ID run silently lost the guard.

Rather than resolve hub metadata, the cap itself has been removed. It was only ever wanted in the offload case, where it is close to free — the run is already streaming weights, and the headroom is required because at 100% packed calibration OOMs immediately. Applied to a model that does fit it would convert a fully resident load into an offloaded one, which is a large certain slowdown introduced where there was none. That is why the original condition fired only on spill, and knowing which side you are on is exactly what needs a constructed model — the thing NVBug 6563509 is about.

So device_map="auto" now sizes itself; from_pretrained runs its own infer_auto_device_map on the real model regardless (transformers/modeling_utils.py:1362). --use_seq_device_map is the supported answer to GPU OOM, and --gpu_max_mem_percentage applies there and to --offload_folder — which is what its help text already said: "the percentage of available GPU memory to use for loading the model when device_map is set to sequential."

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Superseded by 701180e — this code is back to its original form on main.

example_utils.py and its tests are restored to main except for the Phi-specific multimodal-detection heuristics. The meta-device skeleton, infer_auto_device_map, and the --gpu_max_mem_percentage cap all keep their original behavior, so there is nothing left in this thread to act on.

Reasoning: the cap is wanted exactly where it already fires — when the model is already offloading to CPU, where it costs little and the headroom is required. Every alternative explored on this branch (a more permissive skeleton, sizing from the checkpoint index, an unconditional budget) either changed behavior for models that fit today or moved the guard somewhere it does not belong, and all of it was for a crash that only ever affected the checkpoints this PR removes. NVBug 6563509 is resolved by dropping Phi-4-multimodal, not by changing the loader.

Comment thread examples/hf_ptq/example_utils.py Outdated
# Cap the budget in that case. Sized from the checkpoint on disk rather than
# from a constructed model, so no checkpoint ``__init__`` runs here.
checkpoint_bytes = _checkpoint_size_bytes(ckpt_path)
gpu_budget = sum(size for dev, size in max_memory.items() if isinstance(dev, int))

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bot comment.

[P1] Preserve per-device placement when deciding whether to reserve calibration headroom. Total checkpoint bytes fitting within the sum of GPU memory does not mean Accelerate can keep the model off CPU: parameters are indivisible and placement is per device. For example, two 9.5 GiB leaf parameters total 19 GiB across two 10 GiB GPUs, but infer_auto_device_map places one on GPU 1 and one on CPU, leaving the used GPU 95% full. This aggregate check reports that case as fitting and omits max_memory precisely when calibration may OOM. Please use a placement-aware or tensor-aware check, or a conservative condition that cannot produce this false fit, and add a regression test.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The principle is right and the code is gone as of 1b6a79d, but the suggested direction would have been wrong, and it is worth recording why.

On the principle: yes, an aggregate byte count cannot reproduce per-device placement, since parameters are indivisible. (The worked example does not hold — two 9.5 GiB parameters across two 10 GiB GPUs place one each and both fit. Three 6 GiB blocks on two 10 GiB GPUs is the real shape: 18 GiB aggregate against 20 GiB, yet the third spills.)

On the fix: a "conservative condition that cannot produce a false fit" would have been a regression. The cap is only wanted when the model is already offloading, where it is close to free and the headroom is required — at 100% packed, calibration OOMs immediately. Applied to a model that does fit, it converts a fully resident load into an offloaded one: a large, certain slowdown introduced where there was none. So a false "does not fit" is the more expensive error here, and biasing the check toward capping trades a rare loud OOM the user can fix with a flag for a guaranteed slowdown across a whole band of models.

Since the check must not be biased in either direction, and since answering it accurately requires constructing the model — which is the whole of NVBug 6563509 — the cap has been removed instead. device_map="auto" sizes itself; from_pretrained runs its own infer_auto_device_map on the real model anyway. --use_seq_device_map is the supported answer to GPU OOM.

No regression test, because there is no longer a condition to regress.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Superseded by 701180e — this code is back to its original form on main.

example_utils.py and its tests are restored to main except for the Phi-specific multimodal-detection heuristics. The meta-device skeleton, infer_auto_device_map, and the --gpu_max_mem_percentage cap all keep their original behavior, so there is nothing left in this thread to act on.

Reasoning: the cap is wanted exactly where it already fires — when the model is already offloading to CPU, where it costs little and the headroom is required. Every alternative explored on this branch (a more permissive skeleton, sizing from the checkpoint index, an unconditional budget) either changed behavior for models that fit today or moved the guard somewhere it does not belong, and all of it was for a crash that only ever affected the checkpoints this PR removes. NVBug 6563509 is resolved by dropping Phi-4-multimodal, not by changing the loader.

get_model built a throwaway meta-device model, ran infer_auto_device_map on it,
and -- if the weights would spill to CPU -- shrank max_memory by
gpu_max_mem_percentage to leave room for calibration activations.

Answering "will the weights fit" requires constructing the model, and that is
the whole of NVBug 6563509: remote code that derives a scalar from a real tensor
in __init__ (Phi-4-multimodal's conformer does int(torch.tensor(...))) cannot
run under the global torch.device("meta") context accelerate uses for
include_buffers=True. Both Transformers 4.x and 5.x load such models fine, so
the probe was killing loads it existed only to tune.

The probe is gone rather than repaired. Sizing it from the checkpoint on disk
instead was accurate but not free: it silently skipped Hugging Face model IDs
(`--pyt_ckpt_path` accepts a model card, and the README recommends it), and an
aggregate byte count cannot reproduce per-device placement, so it could report a
false fit near capacity.

Neither is worth solving, because the cap is only wanted in the offload case.
There it is close to free -- the run is already streaming weights, and the
headroom is required, since at 100% packed calibration OOMs immediately. Applied
to a model that does fit, it would convert a fully resident load into an
offloaded one: a large, certain slowdown introduced where there was none. That
asymmetry is why the old condition fired only on spill, and it means the check
must not be biased in either direction -- which rules out a conservative
threshold as well.

So device_map="auto" now sizes itself; from_pretrained runs its own
infer_auto_device_map on the real model regardless. --use_seq_device_map is the
supported answer to GPU OOM, and --gpu_max_mem_percentage applies there and to
--offload_folder, matching what its help text already documented ("when
device_map is set to sequential").

Verified Phi-4-multimodal now reaches from_pretrained with no meta-tensor error,
and no model is constructed during device-map selection.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: Chenjie Luo <chenjiel@nvidia.com>
@cjluo-nv cjluo-nv changed the title [NVBug: 6563509] Harden meta-device skeleton build; drop Phi-3-vision / Phi-4-multimodal PTQ support [NVBug: 6563509] Drop Phi-3-vision / Phi-4-multimodal PTQ support; stop auto-capping GPU memory for device_map=auto Aug 10, 2026
examples/hf_ptq/example_utils.py and its tests are restored to main except for
the Phi-specific multimodal-detection heuristics (vision_lora, audio_processor,
embd_layer.image_embd_layer, model_type == "phi4mm"), which only ever matched
the two models being dropped.

The meta-device skeleton, infer_auto_device_map, and the gpu_max_mem_percentage
cap keep their original behavior. The cap is wanted exactly where it already
fires -- when the model is already offloading to CPU, where it is close to free
and the headroom is required. Every alternative explored here (a permissive
skeleton, sizing from the checkpoint index, an unconditional budget) either
changed behavior for models that fit today or moved the guard somewhere it does
not belong, for a crash that only ever affected checkpoints this PR removes.

NVBug 6563509 is therefore resolved by dropping Phi-4-multimodal, not by
changing the loader. A remote-code checkpoint that computes scalars from real
tensors in __init__ will still fail the meta-device build; that is a separate
question if it ever affects a supported model.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: Chenjie Luo <chenjiel@nvidia.com>
@cjluo-nv cjluo-nv changed the title [NVBug: 6563509] Drop Phi-3-vision / Phi-4-multimodal PTQ support; stop auto-capping GPU memory for device_map=auto [NVBug: 6563509] Drop Phi-3-vision / Phi-4-multimodal PTQ support Aug 10, 2026
Comment thread CHANGELOG.rst Outdated

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you please update this part to drop ph4mm?

@cjluo-nv
cjluo-nv enabled auto-merge (squash) August 10, 2026 22:17
0.46 is unreleased, so the same section cannot both cite
modelopt_recipes/huggingface/phi4mm/ as an existing pattern and delete it.
nemotron_vl still ships and carries the example on its own.

The 0.45 and 0.37 mentions are left alone: those releases shipped with the
recipe present, and their entries are a record of what happened then.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: Chenjie Luo <chenjiel@nvidia.com>
@cjluo-nv
cjluo-nv merged commit 9220fac into main Aug 10, 2026
53 of 54 checks passed
@cjluo-nv
cjluo-nv deleted the chenjiel/nvbug-6563509-meta-init-fallback branch August 10, 2026 23:22
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants